package org.afox.util;

import java.lang.reflect.*;
import java.util.*;

/** 
    <P>This class provides a simple mechanism for initializing objects using either a Hashtable
    or a HashMap.  Objects are added to the HashMap (or Hashtable) with a String key.  When the
    "resolve" method is invoked, the Resolver traverses the keys and, using reflection on
    the target object, identifies any appropriate "set" methods.  An appropriate set method
    is defined as follows:
    <PRE>
        setKey(Object ...)
    </PRE>

    <P>The Key is capitalized and prefixed with with word "set".

    <P>The set method is invoked reflectively and the value associated with the key is passed
    as a parameter.  It is the programmers responsibility to ensure that the type of 
    object put in the HashMap is appropriate for the associated set method.

    <P>For a given key, if an appropriate set method is not found, the Resolver just 
    ignores the key.

    <P><B>NOTE:</B> Do not instantiate this class.  Instead use the static "resolve" methods.    
*/

public class Resolver
{
	private static HashMap resolutions = new HashMap();

	private HashMap methods = new HashMap();

	/** This method resolves the target object and the data contained within the HashMap parameter.*/
	public static void resolve(Object target, HashMap data)
	{
		Resolver aResolver = (Resolver) resolutions.get(target.getClass());
		if (aResolver == null)
		{
			aResolver = new Resolver(target.getClass());
			resolutions.put(target.getClass(), new Resolver(target.getClass()));
		}

		aResolver.resolveImpl(target, data);
	}

	/** This method resolves the target object and the data contained within the Hashtable parameter.*/
	public static void resolve(Object anObject, Hashtable data)
	{
		Resolver aResolver = (Resolver) resolutions.get(anObject.getClass());
		if (aResolver == null)
		{
			aResolver = new Resolver(anObject.getClass());
			resolutions.put(anObject.getClass(), new Resolver(anObject.getClass()));
		}

		aResolver.resolveImpl(anObject, data);
	}

	/* When a resolver is instantiated (ie. it cannot be found in the class's list of resolvers
	   for the given class), it is passed a class.  This object is reusable between different
	   instances of the same class.  It is also thread-safe. */
	private Resolver(Class aClass)
	{
		setupMethods(aClass);
	}

	/* This method finds all methods which begin with the word "set" and sets up a system which
	   allows for quick resolution between a Key and a method */
	private void setupMethods(Class aClass)
	{
		Method[] theMethods = aClass.getMethods();
		for (int i = 0; i< theMethods.length; i++)
		{
			if (theMethods[i].getName().toLowerCase().startsWith("set"))
			{
				if (theMethods[i].getParameterTypes().length == 1)
				{
					methods.put(theMethods[i].getName().toLowerCase(), theMethods[i]);
				}
			}
		}
	}

	/* This method actually performs the resolution between an object and a HashMap.  */
	private void resolveImpl(Object anObject, HashMap data)
	{
		Object[] parms = new Object[1];
		Iterator keys = data.keySet().iterator();
		while(keys.hasNext())
		{
			String key = (String) keys.next();
			Method aMethod = (Method) methods.get("set" + key.toLowerCase());
			parms[0] = data.get(key);
			try
			{
				aMethod.invoke(anObject, parms);
			}
			catch (Exception x)
			{
				// Exceptions can be safely ignored here.
			}
		}
	}

	/* This method actually performs the resolution between an object and a Hashtable.  */
	private void resolveImpl(Object anObject, Hashtable data)
	{
		Object[] parms = new Object[1];
		Enumeration enumer = data.keys();
		while(enumer.hasMoreElements())
		{
			String key = (String) enumer.nextElement();
			Method aMethod = (Method) methods.get("set" + key.toLowerCase());
			parms[0] = data.get(key);
			try
			{
				aMethod.invoke(anObject, parms);
			}
			catch (Exception x)
			{
				// ignore if the method isn't found
			}
		}
	}

}
